use std::{mem};

use crate::{User, AppError};

use sqlx::{PgPool, postgres::PgQueryResult};
//use anyhow::{Ok, Result};

use argon2::{
    Argon2, password_hash::{
        self, PasswordHash, PasswordHasher, PasswordVerifier, SaltString, rand_core::OsRng
    }
};


impl User {
    pub async fn find_by_email(email: &str, pool: &PgPool) ->Result<Option<Self>,AppError> {
        let user = sqlx::query_as("SELECT id,fullname,email,created_at FROM users WHERE email = $1")
        .bind(email)
        .fetch_optional(pool)
        .await?;

        Ok(user)
    }

    pub async fn delete_by_id(user_id: &i64, pool: &PgPool) -> Result<PgQueryResult,AppError> {
        let result = sqlx::query("DELETE FROM users where id = $1")
        .bind(user_id)
        .execute(pool)
        .await?;
        Ok(result)
    }

    pub async fn update_email_by_id(user_id: &i64, new_email: &str, pool: &PgPool) -> Result<Self, AppError> {
        let user = sqlx::query_as(r#"
           UPDATE users  set email = $1 where id =$2
           RETURNING id, fullname,email,created_at
        "#,)
        .bind(new_email)
        .bind(user_id)
        .fetch_one(pool)
        .await?;

        Ok(user)
    }



    pub async  fn create(email: &str, fullname: &str, password : &str, pool: &PgPool)->Result<Self,AppError>{

        let password_hash = hash_password(password)?;
        let user = sqlx::query_as(r#"
            INSERT INTO users (email, fullname, password_hash)
            VALUES($1,$2,$3)
            RETURNING id, fullname, email , created_at
        "#,)
        .bind(email)
        .bind(fullname)
        .bind(password_hash)
        .fetch_one(pool)
        .await?;

    Ok(user)

    }



    pub async fn verify(email: &str, password : &str,  pool: &PgPool)  -> Result<Option<Self>,AppError> {
        let user: Option<User> = sqlx::query_as("
            SELECT id, fullname,email, password_hash, created_at FROM  users WHERE emial = $1
        ")
        .bind(email)
        .fetch_optional(pool)
        .await?;
        
        match user {
            Some(mut user) =>{
                let password_hash = mem::take(&mut user.password_hash );
                let is_valid = verify_password(password, &password_hash.unwrap_or_default())?;
                if is_valid {
                    Ok(Some(user))
                }else{
                    Ok(None)
                }
            }

            None => Ok(None),
        }
    }


    
}

fn hash_password(password: &str) -> Result<String,AppError> {
    let salt = SaltString::generate(&mut OsRng);

    // Argon2 with default params (Argon2id v19)
    let argon2 = Argon2::default();

    // Hash password to PHC string ($argon2id$v=19$...)
    let password_hash = argon2.hash_password(password.as_bytes(), &salt)?.to_string();

    // Verify password against PHC string.
    //
    // NOTE: hash params from `parsed_hash` are used instead of what is configured in the
    // `Argon2` instance.
    //let parsed_hash = PasswordHash::new(&password_hash)?;
    Ok(password_hash)
}

fn verify_password(password: &str, password_hash: &str ) -> Result<bool, AppError> {
    let argon2 = Argon2::default();
    let password_hash = PasswordHash::new(password_hash)?;
    let is_valid = argon2.verify_password(password.as_bytes(), &password_hash)
    .is_ok();

    Ok(is_valid)
}

#[cfg(test)]
mod tests {

    use super::*;
    use anyhow::{Ok, Result};
    use sqlx_db_tester::TestPg;
    use std::path::Path;

    #[test]
    fn hash_password_should_work()  -> Result<()> {
        let password = "abkci*73^";
        let password_hash = hash_password(password);
      
        if(password_hash.is_ok()) {
            let value = password_hash.unwrap();
            assert_eq!(value.len(), 97);
            assert!(verify_password(&password, &value)?);
        }
       
        Ok(())
    }

    #[tokio::test]
    async fn create_user_should_work() -> Result<()>{
        // let tdb = TestPg::new(
        //     "postgres://glomia:qwk*7sk@38.186.48.216:5432".to_string(),
        //     Path::new("../migrations"),

        // );

        // let pool = tdb.get_pool().await;
        let url = "postgres://glomia:qwk*7sk@38.186.48.216:5432/chat";
        let pool = PgPool::connect(url).await?;
        let email = "twtqmq@163.com";
        let name = "xu gang";
        let password = "i3erkk*jd^K";
        let user = User::create(email, name, password, &pool).await?;
        assert_eq!(user.email, email);
        assert_eq!(user.fullname, name);

        Ok(())
    }


    #[tokio::test]
    async fn find_by_email_should_work() ->Result<()> {
        let url = "postgres://glomia:qwk*7sk@38.186.48.216:5432/chat";
        let pool = PgPool::connect(url).await?;
        let query_email = "twtqmq@163.com1";
        let query_result = User::find_by_email(query_email, &pool).await?;
        
        assert!(query_result.is_none());
            // let user = query_result.unwrap();
            // assert_eq!(user.email, query_email);
            // assert_eq!(user.fullname, "xu gang");
        
    
        Ok(())

    }


    #[tokio::test]
    async fn update_email_by_id_should_work() ->Result<()> {
        let url = "postgres://glomia:qwk*7sk@38.186.48.216:5432/chat";
        let pool = PgPool::connect(url).await?;
        let new_email = "98281215@qq.com";
        let query_id = &(1 as i64);
        let user = User::update_email_by_id(query_id,new_email, &pool).await?;
        
        assert_eq!(user.email, new_email);
        assert_eq!(user.fullname, "xu gang");
        
    
        Ok(())

    }


    #[tokio::test]
    async fn delete_by_id_should_work() ->Result<()> {
        let url = "postgres://glomia:qwk*7sk@38.186.48.216:5432/chat";
        let pool = PgPool::connect(url).await?;
        let query_id = &(3 as i64);
        let delete_rst = User::delete_by_id(query_id,&pool).await?;
        

 //       assert_eq!(user.email, new_email);
        assert_eq!(delete_rst.rows_affected(),1);
        
    
        Ok(())

    }



}
